#!/usr/bin/python2.4
#
#Copyright 2009 Google Inc.
#
#Licensed under the Apache License, Version 2.0 (the "License");
#you may not use this file except in compliance with the License.
#You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
#Unless required by applicable law or agreed to in writing, software
#distributed under the License is distributed on an "AS IS" BASIS,
#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#See the License for the specific language governing permissions and
#limitations under the License.
#

"""Module for version id encryption and decryption.

  This module implements encryption and decrpytion algorithm for version id. It
  encrypts the version id, which is an integer, into 3-4 byte string containing
  character a-zA-Z. It transforms the integer into another large integer by
  linear equation and maps the digits to characters.
"""

import logging
import digital_content_wizard_exceptions

ALPHABETS = 'abcdefghijklmnopqrstuvwxyz'
MULTIPLIER = 39
OFFSET = 193


def MapNumToChar(number_to_be_mapped):
  """Maps given number to alphabet in a-zA-Z.

  This method first adds to 3 to number and maps it to space 0-25.
  Based on the digit passed, it decides to return upper or lower case of
  alphabet.
  Args:
    number_to_be_mapped: Number to be mapped to alphabet.

  Returns:
    Alphabet mapped to digit.
  """
  number_after_mod = (number_to_be_mapped + 3) % 26
  if number_after_mod < 5:
    return ALPHABETS[number_after_mod]
  return ALPHABETS[number_after_mod].upper()


def Encrypt(version_id):
  """Encrypts the given version id into 3-4 letter string.

  Encryption Algorithm:
    given number x,
    1. y = 35x + 143
    2. For every digit n of y do
      get (n+3)%26 th alphabet from a-zA-Z
      append it into result string
    3. Return result string

  Args:
    version_id: Version id number to be encrypted.

  Returns:
    Encrypted string.
  """
  encrypted_string = ''
  logging.debug('Encrypting version id ' + str(version_id))
  version_id = (MULTIPLIER * version_id + OFFSET)
  while version_id:
    number_after_mod = version_id % 10
    version_id /= 10
    number_mapped_string = MapNumToChar(number_after_mod)
    encrypted_string += number_mapped_string
  logging.debug('Encrypted version id: ' + encrypted_string)
  return encrypted_string


def Decrypt(encrypted_string):
  """Decrypts the encrypted string.

  This method implements decryption algorithm to derypt the string provided and
  returns version id.

  Args:
    encrypted_string: Encrypted string.

  Returns:
    Decrypted version id number.

  Raises:
    EncryptionException: An error occurred when invalid encrypted version id is
    specified.
    InvalidVersionException: An error occurred when decrypted version is
    invalid.
  """
  if not encrypted_string:
    raise digital_content_wizard_exceptions.EncryptionException(
        'Invalid encrypted version id.', encrypted_string)
  logging.debug('Decrypting version id string ' + encrypted_string)
  encrypted_string = encrypted_string.lower()
  number_of_digits = len(encrypted_string)
  digits = []
  i = 0
  while i < number_of_digits:
    alphabet_index = ALPHABETS.find(encrypted_string[i])
    if alphabet_index == -1:
      logging.error('invalid version id')
      raise digital_content_wizard_exceptions.EncryptionException(
          'Invalid encrypted version id', encrypted_string)
    digit = (alphabet_index - 3) % 26
    if digit > 9:
      logging.error('invalid version id')
      raise digital_content_wizard_exceptions.EncryptionException(
          'Invalid encrypted version id', encrypted_string)
    digits.append(digit)
    i += 1
  digits.reverse()
  formed_number = 0
  for digit in digits:
    formed_number = formed_number * 10 + digit
  version_id = (formed_number - OFFSET) / MULTIPLIER
  if version_id < 1:
    raise digital_content_wizard_exceptions.InvalidVersionException(
        'Invalid version id', version_id)
  logging.debug('Decrypted version id: ' + str(version_id))
  return version_id


def TestAlgorithm(version_id):
  """Tests encrpytion and decryption algorithm.

  This function encrypts the given version id and decrypts the result back to
  check whether the decrpyted id is same as the original id.

  Args:
    version_id: Version id number to test.

  Returns:
    True if success; False otherwise.
  """
  encrypted_version_id = Encrypt(version_id)
  if version_id == Decrypt(encrypted_version_id):
    number_mapped_string = str(version_id)
    number_mapped_string += '=>'
    number_mapped_string += str(MULTIPLIER * version_id + OFFSET)
    number_mapped_string += '=>'
    number_mapped_string += encrypted_version_id
    print number_mapped_string
    return True
  else:
    print str(version_id) + ' not matched'
    return False
